// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rbchat.logic.alarm.impl;

import android.util.Log;

import com.eva.android.ArrayListObservable;
import com.x52im.rbchat.MyApplication;
import com.x52im.rbchat.cache.AlarmsProvider;
import com.x52im.rbchat.logic.alarm.model.AlarmDto;

import net.x52im.mobileimsdk.android.utils.MBThreadPoolExecutor;

import java.util.List;
import java.util.Observer;
import java.util.concurrent.atomic.AtomicInteger;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

/**
 * 用于新版首页"消息"列表的ViewModel实现类。
 *
 * @author JackJiang
 * @since 8.0
 */
public class AlarmsViewModel extends ViewModel {

    private final static String TAG = AlarmsViewModel.class.getSimpleName();

    /** 未读"消息"数据变动LiveData对象（这些变动可能是：新增、删除、替换了消息数据） */
    private MutableLiveData<List<AlarmDto>> alarmsLiveData = null;
    /** 未读"消息"总数计算结果LiveData对象（计算总数有大量消息存在的情况下可能需要一些计算资源，后台计算再postValue提升体验） */
    private MutableLiveData<Integer> alarmsUnreadLoadedLiveData = null;
    /** 世界频道消息数据变动LiveData对象 */
    private MutableLiveData<AlarmDto> bbsAlarmLiveData = null;
    /** 网络状态变动LiveData对象 */
    private MutableLiveData<?> netConnectionChangeLiveData = null;

    /** 数据模型变动观察者实现block */
    private Observer listDatasObserver = null;

    /**
     * 载入首页"消息"列表数据的任务记数器。
     * <p>
     * 这是一个支持多线程原子读的标识量（比布尔量使用更灵活且线程安全），当载入数据时+1、载入完成时-1，目的是防止重复加载 */
    private final AtomicInteger loadingAlarmsCounter = new AtomicInteger(0);

    /**
     * 载入首页"消息"列表数据的任务记数器。
     * <p>
     * 这是一个支持多线程原子读的标识量（比布尔量使用更灵活且线程安全），当开始清除时+1、清除完成时-1，目的是防止重复清除 */
    private final AtomicInteger clearAllAlarmsUnreadCounter = new AtomicInteger(0);

    public AlarmsViewModel() {
        this.initLiveData();
    }

    /**
     * 本ViewModel类被回收时将要做的处理。
     */
    @Override
    protected void onCleared() {
        super.onCleared();

        Log.i(TAG, "@@@@3【AlarmsViewModel】onCleared被调用了！");

        // ## 及时取消设置与服务器的连接改变观察者（防止对象被引用而导致本Activity不能被系统回收）
        //    此观察者是在上方的 initViews或其调用的方法中调置的
        MyApplication.getInstance2().getIMClientManager().getBaseEventListener().setNetworkStatusObserver(null);

        // ## 取消"消息"数据变动观察者（防止对象被引用而导致本Activity不能被系统回收）即时解除对象引用，否则
        // ## observer中引用了listAdapter，而adapter又引用了Activity，不解除就会导致Activity无法被回收而浪费内存。
        // 此观察者是在上方的 initViews或其调用的方法中调置的。
        ArrayListObservable<AlarmDto> alarmDatas = MyApplication.getInstance2().getIMClientManager().getAlarmsProvider().getAlarmsData();
        if(alarmDatas != null && listDatasObserver != null)
            alarmDatas.removeObserver(listDatasObserver);

        // ## 取消设置BBS世界频道消息的数据变动观察者（防止对象被引用而导致本Activity不能被系统回收）即时解除对象引用，否则observer中引
        // ## 用了bbsAlarmUIWrapper，而bbsAlarmUIWrapper又引用了Activity，不解除就会导致Activity无法被回收而浪费内存。
        // 此观察者是在上方的 initViews或其调用的方法中调置的
        MyApplication.getInstance2().getIMClientManager().getAlarmsProvider().getBBSAlarmData().setObserver(null);
    }

    /**
     * liveData对象初始化。
     */
    private void initLiveData() {

        Log.i(TAG, "@@@@3【AlarmsViewModel】initLiveData被调用了！");

        if (this.alarmsLiveData == null) {
            this.alarmsLiveData = new MutableLiveData<>();
        }
        if(this.alarmsUnreadLoadedLiveData == null){
            this.alarmsUnreadLoadedLiveData = new MutableLiveData<>();
        }
        if (this.bbsAlarmLiveData == null) {
            this.bbsAlarmLiveData = new MutableLiveData<>();
        }
        if (this.netConnectionChangeLiveData == null) {
            this.netConnectionChangeLiveData = new MutableLiveData<>();
        }

        // "消息"列表数据模型变动观察者，由此此观察者来完成ui的刷新（这样就能保证最新消息能即时显示在界面上了）
        listDatasObserver = (observable, data) -> {
            // 通知ui层（注意：此处不需要传递数据，只需要传递通知即可，因为ui层的
            // 列表中引用的是全局数据模型浅拷贝，不需要在此处再把数据集传来传去）
            alarmsLiveData.postValue(null);
        };
        // 设置首页“消息”的数据变动观察者（通过此观察者可以在有新首页“消息”时能及时刷新界面等，用观察者的目的是使得数据模型能与UI进行代码解偶）
        MyApplication.getInstance2().getIMClientManager().getAlarmsProvider().getAlarmsData().addObserver(listDatasObserver);

        // 设置BBS的提示消息观察者
        Observer bbsMsgObs = (observable, data) -> {
            // 通知ui层
            bbsAlarmLiveData.postValue((AlarmDto)data);
        };
        // 设置BBS世界频道的数据变动观察者（通过此观察者可以在有新聊天消息或指令时
        // 能及时刷新界面等，用观察者的目的是使得数据模型能与UI进行代码解偶）
        MyApplication.getInstance2().getIMClientManager().getAlarmsProvider().getBBSAlarmData().setObserver(bbsMsgObs);

        // IM网络连接状态改变观察者
        Observer netConnectedChangeForAlarmActivityObserver = (observable, data) -> {
            // 通知ui层
            netConnectionChangeLiveData.postValue(null);
        };
        // 设置与服务器的连接改变观察者（当网络断开或再次连通时，底层通过此观察者
        // 通知UI进行显示等，用观察者的目的是使得底层能与UI进行代码解偶，而不是直接引用）
        MyApplication.getInstance2().getIMClientManager().getBaseEventListener().setNetworkStatusObserver(netConnectedChangeForAlarmActivityObserver);
    }

    /**
     * 用于关联界面中，加载"消息"列表数据。
     */
    public void loadAlarmsData(){
        Log.i(TAG, "@@@@3【AlarmsViewModel】loadAlarmsData。。。。");

        // 注意：AlarmsFragment中列表的Adapter对应数据集合，就是IMClientManager中的getAlarmsProvider().getAlarmsData().getDataList()
        // 数据集合的浅拷贝，所以此处post时就不需要再把此集合对象再传回去了，只需要传空，ui层观察者接到通知后，刷新列表显示就可以达到刷新ui层数据显示的目的了
        alarmsLiveData.postValue(null);
    }

    /**
     * 用于关联界面中，首次加载"消息"列表数据（首次加载指的是app启动后从sqlite本地缓存中读取历史记录到内存模型中）。
     *
     * 注意：此方法post给ui层的对象是全局群列表数据模型的浅拷贝，后续数据模型的数据改动，经LiveData的通知，只需要
     * 让ui层RecycleView即时调用notifyDataSetChanged()就能刷新最新数据显示，而不需要重复传递整个群列表数据了。
     */
    public void loadAlarmsDataOnce() {

        Log.i(TAG, "@@@@3【AlarmsViewModel】loadAlarmsDataOnce。。。。");

        // 加载指示标识（用于防止多线程重复加载）
        int count = loadingAlarmsCounter.get();
        if (count > 0) {
            return;
        }

        // 加载开始时指示标识+1
        loadingAlarmsCounter.incrementAndGet();

        // 数据处理逻辑在后台进行，提升ui体验
//      MBThreadPoolExecutor.runInBackground(() -> {
            long t = System.currentTimeMillis();
            // 加载完成时指示标识-1
            loadingAlarmsCounter.decrementAndGet();

            // 载入历史数据、预定义数据等
            MyApplication.getInstance2().getIMClientManager().getAlarmsProvider().loadDatasOnce();
            // 通知ui层
            loadAlarmsData();
//      });
    }

    /**
     * 未读"消息"总数.
     */
    public void loadAlarmsUnread(){
        MBThreadPoolExecutor.runInBackground(() -> {
            // 未读消息总数
            int totalUnreadNum = MyApplication.getInstance2().getIMClientManager().getAlarmsProvider().getTotalFlagNum();
            // 通知ui层
            alarmsUnreadLoadedLiveData.postValue(totalUnreadNum);
        });
    }

    /**
     * 清除"消息"列表所有item数据中的未读数和世界频道快捷组件上的未读数。
     */
    public void clearAllAlarmsAndBBSUnread(){

        Log.i(TAG, "@@@@3【AlarmsViewModel】clearAllAlarmsAndBBSUnread。。。。");

        // 执行指示标识（用于防止多线程重复加载）
        int count = clearAllAlarmsUnreadCounter.get();
        if (count > 0) {
            return;
        }

        // 执行开始时指示标识+1
        clearAllAlarmsUnreadCounter.incrementAndGet();

        // 数据处理逻辑在后台进行，提升ui体验
        MBThreadPoolExecutor.runInBackground(() -> {
            long t = System.currentTimeMillis();
            // 执行完成时指示标识-1
            clearAllAlarmsUnreadCounter.decrementAndGet();

            AlarmsProvider ap = MyApplication.getInstance2().getIMClientManager().getAlarmsProvider();

            // 先清除首页"消息列表"中所有item数据对象中的未读数
            ap.resetAllFlagNum(MyApplication.getInstance2(), true);
            // 通知ui层刷新最新数据的显示
            alarmsLiveData.postValue(null);

            // 再清除首页中"世界频道"快键组件上的未读数（记得不需要通知观察者，因为ui层会通过下面的postValue更新）
            ap.getBBSAlarmData().resetFlagNum(false);
            // 通知ui层刷新最新数据的显示
            bbsAlarmLiveData.postValue(MyApplication.getInstance2().getIMClientManager().getAlarmsProvider().getBBSAlarmData().getData());

            Log.i(TAG, "@@@@3【AlarmsViewModel】clearAllAlarmsAndBBSUnread完成，耗时：" + (System.currentTimeMillis() - t) + "ms");
        });
    }

    public MutableLiveData<List<AlarmDto>> getAlarmsLiveData() {
        return alarmsLiveData;
    }

    public MutableLiveData<Integer> getAlarmsUnreadLoadedLiveData() {
        return alarmsUnreadLoadedLiveData;
    }

    public MutableLiveData<AlarmDto> getBbsAlarmLiveData() {
        return bbsAlarmLiveData;
    }

    public MutableLiveData<?> getNetConnectionChangeLiveData() {
        return netConnectionChangeLiveData;
    }
}
